/***
    LSM 
*/
#include "LSM.hh"

LSM::LSM(int x, int y, int z) : _X(x), _Y(y), _Z(z)
{
  // connection probability parameter
  lambda = 1.0;

  // fraction allocation parameter
  max_fraction = 0;


  _conf.setCudaBackend();

  initialized = false;
}

void LSM::init(void)
{

  // Add all neuron types and map their Nemo IDs
  std::map<std::string,int> type_index;
  for( std::set<std::string>::iterator it = nemoTypes.begin(); it != nemoTypes.end(); it++)
    {
      int id = _net.addNeuronType(*it);
      type_index.insert(std::pair<std::string,int>(*it,id));
    }
   
  srand(time(NULL));
  
  // init the LSM neurons
  int num_neurons = _X * _Y * _Z;
  for(int ni = 0; ni < num_neurons; ni++) {
    
    //add neuron
    NeuronType* nt = neuronTypeToAdd();
    _net.addNeuron(type_index[nt->getType()], ni, nt->getNumParams(), nt->getParams());

    // the current neuron index
    pos i = indx2pos(ni);
    
    // Set the synapses
    for(int nj = 0; nj < num_neurons; nj++) {

      pos j = indx2pos(nj);
      
      // euclidian distance i-to-j, and associated conduction delay
      float D_ij = sqrt ( pow((i.x - j.x),2) + pow((i.y - j.y),2) + pow((i.z - j.z),2) );
      unsigned int delay = floor(D_ij) + 1;

      // probability of connection
      float P_ij = exp (-D_ij/(lambda*lambda));
      float die_roll = (float)rand()/RAND_MAX;

      // Randomized weight
      float weight = nt->generateOutboundWeight();

      // if actual roll-of-the-dice < P(i,j)
      if(  die_roll < P_ij && ni != nj )
	_net.addSynapse(ni, nj, delay, weight, false);
      
    } 
  }

  // init the Input Layer neurons
  int inputTypeID = _net.addNeuronType("Input");
  int inputlayer_neurons_maxid = (_X * _Y) + num_neurons;
  
  // add after LSM internal neurons
  for(int i = num_neurons; i < inputlayer_neurons_maxid; i++) {
    // create the neuron and connection
    _net.addNeuron(inputTypeID, i, 0, NULL);    
    _net.addSynapse(i, i-num_neurons, 1, 1, false);
    
  }

  // init the simulation
   _sim = nemo::simulation(_net, _conf);

  initialized = true;

  std::cout << "CUDA:" << _conf.backendDescription() << std::endl;
}

LSM::~LSM(void)
{

}

const vector<unsigned>  LSM::step()
{
  return _sim->step();
}

void LSM::addNeuronType(NeuronType* nt, float fraction)
{
  // Add Nemo neuron type to list
  if( nemoTypes.find( nt->getType() ) == nemoTypes.end() )
    nemoTypes.insert( nt->getType() );
  
  float allocation_index = max_fraction + fraction;
  neuron_allocation_map[allocation_index] = nt;
  max_fraction = allocation_index;

  std::cout << max_fraction << std::endl;
}

NeuronType* LSM::neuronTypeToAdd(void)
{
  NeuronType* nt = 0;

  float prev_share = 0;
  float die_roll = ((float)rand()/RAND_MAX) * max_fraction;

  std::map<float, NeuronType*>::iterator nt_iter = neuron_allocation_map.begin();
  while(nt_iter != neuron_allocation_map.end()){
    if( die_roll <= nt_iter->first ) {
	nt = nt_iter->second;
	break;
    }
    nt_iter++;
  }
  return nt;
}
